package storage

import (
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/holiman/uint256"
)

//本项目设计的EVM访问的时候可以访问的Storage相关的数据由四部分组成：
//ExecuteCache会记录该笔交易在执行的时候涉及到的状态的修改
//BatchCache:为批处理的时候负责管理的一部分的中间状态，对于缓存未命中的数据会调用StateDB的接口从此难过外部获取，并且把获取到的结果放到BatchCache中。每一轮执行完成后都会将transaction的执行结果整合到BatchCache中，最后生成区块的时候再将BatchCache的结果提交到外存中
//StateDB:为模仿Geth写的一些EVM访问外存时的接口。在BatchEvm的设计中，处理交易时候只会调用只读的函数，只有在最终处理完成后才会将结果提交到外存中。
//ChainDB：是访问Blockchain上信息的一组接口，在Geth中，这些函数被分布在各个数据结构中，这里我把它统一称为ChainDB接口
type BatchStorage struct {
	ExecuteCache *ExecuteCache
	BatchCache   *BatchCache
	StateDB
	ChainDB
}

func New(Exe *ExecuteCache, Batch *BatchCache, stateDb StateDB, chainDb ChainDB) *BatchStorage {
	Bs := &BatchStorage{
		ExecuteCache: Exe,
		BatchCache:   Batch,
		StateDB:      stateDb,
		ChainDB:      chainDb,
	}
	return Bs
}

func (Bs *BatchStorage) GetBalance(addr common.Address) *uint256.Int {
	//判断目标Address是否已经被删除,如果删除了则直接返回0
	if Bs.ExecuteCache.IsDestruct {
		if _, ok := Bs.ExecuteCache.DestructCache[addr]; ok {
			return uint256.NewInt(0)
		}
	}
	//先从ExecuteCache中试着拿一下
	balance, ok := Bs.ExecuteCache.BalanceCache[addr]
	if !ok {
		//没命中（大概率）
		//先从BatchCache拿
		balance, ok := Bs.BatchCache.GetBalance(addr)
		if ok {
			return balance.Clone()
		}
		//拿不到就从StateDB拿，拿完放到BachCache.BalanceCache中
		balance = Bs.StateDB.GetBalance(addr).Clone()
		Bs.BatchCache.SetBalance(addr, balance)
		return balance
	} else {
		return balance.Clone()
	}
}

func (Bs *BatchStorage) GetBlockHash(number uint64) common.Hash {
	//首先在BatchCache里面拿
	hash, ok := Bs.BatchCache.GetBlockHash(number)
	if ok {
		return hash
	} else {
		//没拿到掉chainDB的接口从外面拿
		hash = Bs.ChainDB.GetBlockHash(number)
		//拿完之后放到BatchCache中
		Bs.BatchCache.SetBlockHash(number, hash)
		return hash
	}
}

func (Bs *BatchStorage) GetCodeSize(addr common.Address) int {
	//首先防止该合约已经被删除
	if Bs.ExecuteCache.IsDestruct {
		if _, ok := Bs.ExecuteCache.DestructCache[addr]; ok {
			return 0
		}
	}
	//其次判断是否本交易执行的时候是否会创建合约
	if !Bs.ExecuteCache.IsCreate {
		//没有创建的话就先从BatchCache拿
		codeSize, flag := Bs.BatchCache.GetCodeSize(addr)
		if flag {
			return codeSize
		}
		//拿不到就从StateDB拿，拿完放到ExecuteCache中
		codeSize = Bs.StateDB.GetCodeSize(addr)
		Bs.BatchCache.SetCodeSize(addr, codeSize)
		return codeSize
	}
	//如果创建了合约就检查是否是新创建的合约
	contract, flag := Bs.ExecuteCache.NewContractCache[addr]
	if flag {
		return contract.CodeSize
	} else {
		//没命中就从BatchCache拿
		codeSize, flag := Bs.BatchCache.GetCodeSize(addr)
		if flag {
			return codeSize
		}
		//拿不到就从StateDB拿，拿完放到ExecuteCache中
		codeSize = Bs.StateDB.GetCodeSize(addr)
		Bs.BatchCache.SetCodeSize(addr, codeSize)
		return codeSize
	}
}

func (Bs *BatchStorage) GetCode(addr common.Address) []byte {
	//判断该合约是否已经被删除
	if Bs.ExecuteCache.IsDestruct {
		if _, ok := Bs.ExecuteCache.DestructCache[addr]; ok {
			return nil
		}
	}
	//首先判断是否执行的时候是否创建了新的合约
	if !Bs.ExecuteCache.IsCreate {
		//先从BatCache拿
		code, ok := Bs.BatchCache.GetCode(addr)
		if ok {
			return code
		}
		//拿不到从StateDB拿
		code = Bs.StateDB.GetCode(addr)
		Bs.BatchCache.SetCode(addr, code)
		return code
	}
	contract, flag := Bs.ExecuteCache.NewContractCache[addr]
	if flag {
		return contract.Code
	} else {
		//先从BatCache拿
		code, ok := Bs.BatchCache.GetCode(addr)
		if ok {
			return code
		}
		//拿不到从StateDB拿
		code = Bs.StateDB.GetCode(addr)
		Bs.BatchCache.SetCode(addr, code)
		return code
	}
}

func (Bs *BatchStorage) GetState(addr common.Address, hash common.Hash) common.Hash {
	//判断addr是否已经被删除了
	if Bs.ExecuteCache.IsDestruct {
		if _, ok := Bs.ExecuteCache.DestructCache[addr]; ok {
			return common.Hash{}
		}
	}
	//首先在executeCache拿
	value, ok := Bs.ExecuteCache.GetState(addr, hash)
	if ok {
		return value
	}
	//其次从BatchCache拿
	value, ok = Bs.BatchCache.GetState(addr, hash)
	if ok {
		return value
	}
	//还不到就从StateDB拿，然后放到BatchCache中
	data := Bs.StateDB.GetState(addr, hash)
	value = data.Bytes32()
	Bs.BatchCache.SetState(addr, hash, value)
	return value
}

func (Bs *BatchStorage) SetState(addr common.Address, hash common.Hash, value common.Hash) {
	//只放到executeCache中
	Bs.ExecuteCache.SetState(addr, hash, value)
}

func (Bs *BatchStorage) AddLog(addr common.Address, log *types.Log) {
	Bs.ExecuteCache.AddLog(addr, log)
}

func (Bs *BatchStorage) BalanceModify(addr common.Address, value *uint256.Int, neg bool) {
	_, ok := Bs.ExecuteCache.BalanceCache[addr]
	if !ok {
		Bs.ExecuteCache.BalanceCache[addr] = Bs.GetBalance(addr)
	}
	if neg {
		Bs.ExecuteCache.BalanceCache[addr].Sub(Bs.ExecuteCache.BalanceCache[addr], value)
	} else {
		Bs.ExecuteCache.BalanceCache[addr].Add(Bs.ExecuteCache.BalanceCache[addr], value)
	}
}

func (Bs *BatchStorage) Destruct(addr common.Address) {
	if !Bs.ExecuteCache.IsDestruct {
		Bs.ExecuteCache.IsDestruct = true
	}
	Bs.ExecuteCache.DestructCache[addr] = true
}

func (Bs *BatchStorage) CanTransfer(addr common.Address, value *uint256.Int) bool {
	balance := Bs.GetBalance(addr)
	if balance.Cmp(value) >= 0 {
		return true
	} else {
		return false
	}
}

func (Bs *BatchStorage) Transfer(from common.Address, to common.Address, value *uint256.Int) {
	//from账户扣钱，to账户加钱
	Bs.BalanceModify(from, value, true)
	Bs.BalanceModify(to, value, false)
}

func (Bs *BatchStorage) RevertToExecuteCache(copy *ExecuteCache) {
	Bs.ExecuteCache = copy
}

func (Bs *BatchStorage) GetNonce(addr common.Address) uint64 {
	if Bs.ExecuteCache.IsDestruct {
		if _, ok := Bs.ExecuteCache.DestructCache[addr]; ok {
			return uint64(0)
		}
	}
	nonce, ok := Bs.ExecuteCache.NonceCache[addr]
	if ok {
		//说明本Trx在执行的过程中修改了nonce
		return nonce
	} else {
		//先从BatchCache拿
		nonce, ok := Bs.BatchCache.GetNonce(addr)
		if ok {
			return nonce
		}
		nonce = Bs.StateDB.GetNonce(addr)
		Bs.BatchCache.SetNonce(addr, nonce)
		return nonce
	}
}

func (Bs *BatchStorage) SetNonce(addr common.Address, value uint64) {
	Bs.ExecuteCache.NonceCache[addr] = value
}

func (Bs *BatchStorage) CreateAccount(addr common.Address, nonce uint64) {
	Bs.ExecuteCache.IsCreate = true
	Bs.ExecuteCache.NewContractCache[addr] = &newContract{Nonce: nonce}
}

func (Bs *BatchStorage) SetNewCode(addr common.Address, code []byte) {
	Bs.ExecuteCache.NewContractCache[addr].Code = code
	Bs.ExecuteCache.NewContractCache[addr].CodeHash = crypto.Keccak256Hash(code)
	Bs.ExecuteCache.NewContractCache[addr].CodeSize = len(code)
}
